/*! @azure/msal-common v7.6.0 2022-10-10 */
'use strict';
import { __assign } from '../../_virtual/_tslib.js';
import { PerformanceEventStatus } from './PerformanceEvent.js';

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
var PerformanceClient = /** @class */ (function () {
    /**
     * Creates an instance of PerformanceClient,
     * an abstract class containing core performance telemetry logic.
     *
     * @constructor
     * @param {string} clientId Client ID of the application
     * @param {string} authority Authority used by the application
     * @param {Logger} logger Logger used by the application
     * @param {string} libraryName Name of the library
     * @param {string} libraryVersion Version of the library
     */
    function PerformanceClient(clientId, authority, logger, libraryName, libraryVersion, applicationTelemetry) {
        this.authority = authority;
        this.libraryName = libraryName;
        this.libraryVersion = libraryVersion;
        this.applicationTelemetry = applicationTelemetry;
        this.clientId = clientId;
        this.logger = logger;
        this.callbacks = new Map();
        this.eventsByCorrelationId = new Map();
        this.measurementsById = new Map();
    }
    /**
     * Starts measuring performance for a given operation. Returns a function that should be used to end the measurement.
     *
     * @param {PerformanceEvents} measureName
     * @param {?string} [correlationId]
     * @returns {InProgressPerformanceEvent}
     */
    PerformanceClient.prototype.startMeasurement = function (measureName, correlationId) {
        var _this = this;
        var _a, _b;
        // Generate a placeholder correlation if the request does not provide one
        var eventCorrelationId = correlationId || this.generateId();
        if (!correlationId) {
            this.logger.info("PerformanceClient: No correlation id provided for " + measureName + ", generating", eventCorrelationId);
        }
        this.logger.trace("PerformanceClient: Performance measurement started for " + measureName, eventCorrelationId);
        var performanceMeasurement = this.startPerformanceMeasuremeant(measureName, eventCorrelationId);
        performanceMeasurement.startMeasurement();
        var inProgressEvent = {
            eventId: this.generateId(),
            status: PerformanceEventStatus.InProgress,
            authority: this.authority,
            libraryName: this.libraryName,
            libraryVersion: this.libraryVersion,
            appName: (_a = this.applicationTelemetry) === null || _a === void 0 ? void 0 : _a.appName,
            appVersion: (_b = this.applicationTelemetry) === null || _b === void 0 ? void 0 : _b.appVersion,
            clientId: this.clientId,
            name: measureName,
            startTimeMs: Date.now(),
            correlationId: eventCorrelationId,
        };
        // Store in progress events so they can be discarded if not ended properly
        this.cacheEventByCorrelationId(inProgressEvent);
        this.cacheMeasurement(inProgressEvent, performanceMeasurement);
        // Return the event and functions the caller can use to properly end/flush the measurement
        return {
            endMeasurement: function (event) {
                var completedEvent = _this.endMeasurement(__assign(__assign({}, inProgressEvent), event));
                if (completedEvent) {
                    // Cache event so that submeasurements can be added downstream
                    _this.cacheEventByCorrelationId(completedEvent);
                }
                return completedEvent;
            },
            flushMeasurement: function () {
                return _this.flushMeasurements(inProgressEvent.name, inProgressEvent.correlationId);
            },
            discardMeasurement: function () {
                return _this.discardMeasurements(inProgressEvent.correlationId);
            },
            measurement: performanceMeasurement,
            event: inProgressEvent
        };
    };
    /**
     * Stops measuring the performance for an operation. Should only be called directly by PerformanceClient classes,
     * as consumers should instead use the function returned by startMeasurement.
     *
     * @param {PerformanceEvent} event
     * @returns {(PerformanceEvent | null)}
     */
    PerformanceClient.prototype.endMeasurement = function (event) {
        var performanceMeasurement = this.measurementsById.get(event.eventId);
        if (performanceMeasurement) {
            // Immediately delete so that the same event isnt ended twice
            this.measurementsById.delete(event.eventId);
            performanceMeasurement.endMeasurement();
            var durationMs = performanceMeasurement.flushMeasurement();
            // null indicates no measurement was taken (e.g. needed performance APIs not present)
            if (durationMs !== null) {
                this.logger.trace("PerformanceClient: Performance measurement ended for " + event.name + ": " + durationMs + " ms", event.correlationId);
                var completedEvent = __assign(__assign({ 
                    // Allow duration to be overwritten when event ends (e.g. testing), but not status
                    durationMs: Math.round(durationMs) }, event), { status: PerformanceEventStatus.Completed });
                return completedEvent;
            }
            else {
                this.logger.trace("PerformanceClient: Performance measurement not taken", event.correlationId);
            }
        }
        else {
            this.logger.trace("PerformanceClient: Measurement not found for " + event.eventId, event.correlationId);
        }
        return null;
    };
    /**
     * Upserts event into event cache.
     * First key is the correlation id, second key is the event id.
     * Allows for events to be grouped by correlation id,
     * and to easily allow for properties on them to be updated.
     *
     * @private
     * @param {PerformanceEvent} event
     */
    PerformanceClient.prototype.cacheEventByCorrelationId = function (event) {
        var existingEvents = this.eventsByCorrelationId.get(event.correlationId);
        if (existingEvents) {
            this.logger.trace("PerformanceClient: Performance measurement for " + event.name + " added/updated", event.correlationId);
            existingEvents.set(event.eventId, event);
        }
        else {
            this.logger.trace("PerformanceClient: Performance measurement for " + event.name + " started", event.correlationId);
            this.eventsByCorrelationId.set(event.correlationId, new Map().set(event.eventId, event));
        }
    };
    /**
     * Cache measurements by their id.
     *
     * @private
     * @param {PerformanceEvent} event
     * @param {IPerformanceMeasurement} measurement
     */
    PerformanceClient.prototype.cacheMeasurement = function (event, measurement) {
        this.measurementsById.set(event.eventId, measurement);
    };
    /**
     * Gathers and emits performance events for measurements taked for the given top-level API and correlation ID.
     *
     * @param {PerformanceEvents} measureName
     * @param {string} correlationId
     */
    PerformanceClient.prototype.flushMeasurements = function (measureName, correlationId) {
        var _this = this;
        this.logger.trace("PerformanceClient: Performance measurements flushed for " + measureName, correlationId);
        var eventsForCorrelationId = this.eventsByCorrelationId.get(correlationId);
        if (eventsForCorrelationId) {
            this.discardMeasurements(correlationId);
            /*
             * Manually end incomplete submeasurements to ensure there arent orphaned/never ending events.
             * Incomplete submeasurements are likely an instrumentation bug that should be fixed.
             * IE only supports Map.forEach.
             */
            var completedEvents_1 = [];
            eventsForCorrelationId.forEach(function (event) {
                if (event.name !== measureName && event.status !== PerformanceEventStatus.Completed) {
                    _this.logger.trace("PerformanceClient: Incomplete submeasurement " + event.name + " found for " + measureName, correlationId);
                    var completedEvent = _this.endMeasurement(event);
                    if (completedEvent) {
                        completedEvents_1.push(completedEvent);
                    }
                }
                completedEvents_1.push(event);
            });
            // Sort events by start time (earliest first)
            var sortedCompletedEvents = completedEvents_1.sort(function (eventA, eventB) { return eventA.startTimeMs - eventB.startTimeMs; });
            // Take completed top level event and add completed submeasurements durations as properties
            var topLevelEvents = sortedCompletedEvents.filter(function (event) { return event.name === measureName && event.status === PerformanceEventStatus.Completed; });
            if (topLevelEvents.length > 0) {
                /*
                 * Only take the first top-level event if there are multiple events with the same correlation id.
                 * This greatly simplifies logic for submeasurements.
                 */
                if (topLevelEvents.length > 1) {
                    this.logger.verbose("PerformanceClient: Multiple distinct top-level performance events found, using the first", correlationId);
                }
                var topLevelEvent = topLevelEvents[0];
                this.logger.verbose("PerformanceClient: Measurement found for " + measureName, correlationId);
                // Build event object with top level and sub measurements
                var eventToEmit = sortedCompletedEvents.reduce(function (previous, current) {
                    if (current.name !== measureName) {
                        _this.logger.trace("PerformanceClient: Complete submeasurement found for " + current.name, correlationId);
                        // TODO: Emit additional properties for each subMeasurement
                        var subMeasurementName = current.name + "DurationMs";
                        /*
                         * Some code paths, such as resolving an authority, can occur multiple times.
                         * Only take the first measurement, since the second could be read from the cache,
                         * or due to the same correlation id being used for two distinct requests.
                         */
                        if (!previous[subMeasurementName]) {
                            previous[subMeasurementName] = current.durationMs;
                        }
                        else {
                            _this.logger.verbose("PerformanceClient: Submeasurement for " + measureName + " already exists for " + current.name + ", ignoring", correlationId);
                        }
                        if (current.accessTokenSize) {
                            previous.accessTokenSize = current.accessTokenSize;
                        }
                        if (current.idTokenSize) {
                            previous.idTokenSize = current.idTokenSize;
                        }
                        if (current.refreshTokenSize) {
                            previous.refreshTokenSize = current.refreshTokenSize;
                        }
                    }
                    return previous;
                }, topLevelEvent);
                this.emitEvents([eventToEmit], eventToEmit.correlationId);
            }
            else {
                this.logger.verbose("PerformanceClient: No completed top-level measurements found for " + measureName, correlationId);
            }
        }
        else {
            this.logger.verbose("PerformanceClient: No measurements found", correlationId);
        }
    };
    /**
     * Removes measurements for a given correlation id.
     *
     * @param {string} correlationId
     */
    PerformanceClient.prototype.discardMeasurements = function (correlationId) {
        this.logger.trace("PerformanceClient: Performance measurements discarded", correlationId);
        this.eventsByCorrelationId.delete(correlationId);
    };
    /**
     * Registers a callback function to receive performance events.
     *
     * @param {PerformanceCallbackFunction} callback
     * @returns {string}
     */
    PerformanceClient.prototype.addPerformanceCallback = function (callback) {
        var callbackId = this.generateId();
        this.callbacks.set(callbackId, callback);
        this.logger.verbose("PerformanceClient: Performance callback registered with id: " + callbackId);
        return callbackId;
    };
    /**
     * Removes a callback registered with addPerformanceCallback.
     *
     * @param {string} callbackId
     * @returns {boolean}
     */
    PerformanceClient.prototype.removePerformanceCallback = function (callbackId) {
        var result = this.callbacks.delete(callbackId);
        if (result) {
            this.logger.verbose("PerformanceClient: Performance callback " + callbackId + " removed.");
        }
        else {
            this.logger.verbose("PerformanceClient: Performance callback " + callbackId + " not removed.");
        }
        return result;
    };
    /**
     * Emits events to all registered callbacks.
     *
     * @param {PerformanceEvent[]} events
     * @param {?string} [correlationId]
     */
    PerformanceClient.prototype.emitEvents = function (events, correlationId) {
        var _this = this;
        this.logger.verbose("PerformanceClient: Emitting performance events", correlationId);
        this.callbacks.forEach(function (callback, callbackId) {
            _this.logger.trace("PerformanceClient: Emitting event to callback " + callbackId, correlationId);
            callback.apply(null, [events]);
        });
    };
    return PerformanceClient;
}());

export { PerformanceClient };
//# sourceMappingURL=PerformanceClient.js.map
